﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NAudio;
using NAudio.Wave;
using System.Threading;
using System.Windows.Media.Animation;
using LaunchAllaround.DesignPattern.MVC;
using LaunchAllaround.DesignPattern;
using System.IO;
using System.Media;

namespace LaunchAllaround.Sound
{
    public class SongPlayer : Model<SongPlayer>
    {
        public static readonly SongPlayer myself = new SongPlayer();
        

        public Boolean IsAValidSong(string songPath)
        {
            try
            {
                AudioFileReader a = new AudioFileReader(songPath);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private WaveStream mainStream;
        private SoundPlayer bpmPlayer;
        private SoundPlayer measurePlayer;
        
        private Stream bpmStream;
        private Stream measureStream;

        private SongPlayer()
        {
            this.bpmStream = Properties.Resources.bpm;
            this.bpmPlayer = new SoundPlayer(this.bpmStream);
            this.bpmPlayer.Load();
            this.measureStream = Properties.Resources.measure;
            this.measurePlayer = new SoundPlayer(this.measureStream);
            this.mainStream = new WaveStream(null);
            this.bpm = 90;
            this.measure = 4;
            this.muteBpm = true;
            this.modelimpl = new ModelImpl<SongPlayer>();
            this.AddView(new AudioSettings(this));
        }

        private int measure;
        public int Measure 
        { 
            get { return this.measure; } 
            set 
            {
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException("Measure must be greater than 1");
                } 
                else if (value > 20)
                {
                    throw new ArgumentOutOfRangeException("Measure must be lower than 20");
                }
                this.measure = value;
                this.RefreshViews(new RefreshMeasure(this));
            } 
        }
        private int bpm;
        public int Bpm 
        { 
            get { return this.bpm; } 
            set 
            {
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException("Bpm must be greater than 1");
                }
                this.bpm = value;
                this.RefreshViews(new RefreshBpm(this));
            }
        }
        private Boolean muteBpm;
        public Boolean MuteBpm 
        { 
            get { return this.muteBpm; } 
            set 
            { 
                this.muteBpm = value;
                if (this.muteBpm && this.Sequencer.IsAlive)
                {
                    this.Sequencer.Abort();
                }
                else if (!this.muteBpm && (this.Sequencer == null || !this.Sequencer.IsAlive))
                {
                    this.Sequencer = new Thread(this.sequencing);
                    this.Sequencer.Start();
                }
                this.RefreshViews(new RefreshMuteBpm(this));
            }
        }

        
        public AudioFileReader CreateStream(Song song)
        {
            return this.CreateStream(song.SongPath, song.Volume);
        }

        public AudioFileReader CreateStream(string songPath, float volume)
        {
            AudioFileReader s = new AudioFileReader(songPath);
            s.Volume = volume;
            return s;
        }


        public void Stop()
        {
            this.mainStream.CloseTarget();         
        }

        public void Play(WaveStream stream)
        {
            stream.Stop();
            stream.Play();
        }
        public void Play(Song song)
        {
            this.mainStream.Play(song);
        }

        private void playBpm()
        {
            this.bpmPlayer.Play();
        }

        
        private void playMeasure()
        {
            this.measurePlayer.Play();
        }

        public Thread Sequencer;
        private void sequencing()
        {
            DateTime playPrevTime = DateTime.Now;
            DateTime metroPrevTime = DateTime.Now;
            int bpmCount = 0;
            try
            {
                while (!this.MuteBpm)
                {
                    int deltaBpmMs = 60000 / this.Bpm; // metronome tick
                    DateTime currentTime = DateTime.Now;

                    // metronome managment
                    if ((currentTime - metroPrevTime).TotalMilliseconds >= deltaBpmMs)
                    {
                        metroPrevTime = currentTime;
                        if (bpmCount % this.measure == 0)
                        {
                            this.playMeasure();
                        }
                        else
                        {
                            this.playBpm();
                        }
                        bpmCount++;
                    }
                }
            }
            catch
            {
                this.Sequencer.Abort();
            }
            this.Sequencer.Abort();
        }

        // model functionalities
        private ModelImpl<SongPlayer> modelimpl;

        public void AddView(View<SongPlayer> v)
        {
            this.modelimpl.AddView(v);
        }

        public Container<View<SongPlayer>> GetViews()
        {
            return this.modelimpl.GetViews();
        }

        public void ClearViews()
        {
            this.modelimpl.ClearViews();
        }

        public void RefreshViews(RefreshEvent<SongPlayer> ev)
        {
            this.modelimpl.RefreshViews(ev);
        }

        public PlaybackState PlaybackState{
            get { return this.mainStream.PlaybackState; }
        }
    }
}
